﻿/* *
 * =============================================================================
 * File Name:               TwoInputXorGate.cs
 * Programmer:              Ken Watts
 * Creation Date:           8/3/2008
 * =============================================================================
 * Description:             Class that represents a two input XOR gate.
 * =============================================================================
 * Modification Date:       8/3/2008 - Initial Revision
 * =============================================================================
 * */

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace DigitalLogicSimulator
{
    public class TwoInputXorGate : Device
    {
        // Constants
        private const int k_int_num_inputs = 2;
        private const int k_int_input_x_offset = 2;
        private const int k_int_input_y_offset = 1;
        private const int k_int_first_input = 0;
        private const int k_int_second_input = 1;
        private const double k_initial_top = 65;
        private const double k_initial_left = 5;
        private const double k_rotational_angle = 180;
        private const double k_radius = 10;
        // Perimeter points
        private const double k_p1_x = 25;
        private const double k_p1_y = 20;
        private const double k_p2_x = 15;
        private const double k_p2_y = 20;
        private const double k_p3_x = 15;
        private const double k_p3_y = 0;
        private const double k_p4_x = 25;
        private const double k_p4_y = 0;
        // Inputs and outputs
        private const double k_in1_p1_x = 0;
        private const double k_in1_p1_y = 5;
        private const double k_in1_p2_x = 17;
        private const double k_in1_p2_y = 5;
        private const double k_in2_p1_x = 0;
        private const double k_in2_p1_y = 15;
        private const double k_in2_p2_x = 17;
        private const double k_in2_p2_y = 15;
        private const double k_out_p1_x = 40;
        private const double k_out_p1_y = 10;
        private const double k_out_p2_x = 50;
        private const double k_out_p2_y = 10;

        // Input states
        bool input1State = false, input2State = false;

        public TwoInputXorGate()
        {
            // Create an array for the input devices
            inputDevices = new Device[k_int_num_inputs];
        }

        protected override void ReplaceInstance(Panel parentPanel)
        {
            parentPanel.Children.Add(new TwoInputXorGate());
        }

        protected override void Draw()
        {
            pathGeometry = new PathGeometry();
            pathGeometry.FillRule = FillRule.Nonzero;
            PathFigure pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_p1_x, k_p1_y);
            pathFigure.IsClosed = true;
            pathGeometry.Figures = new PathFigureCollection();
            pathGeometry.Figures.Add(pathFigure);
            pathFigure.Segments = new PathSegmentCollection();

            // Draw outline 
            LineSegment ls = new LineSegment();
            ls.Point = new Point(k_p2_x, k_p2_y);
            pathFigure.Segments.Add(ls);
            ArcSegment ars = new ArcSegment();
            ars.Point = new Point(k_p3_x, k_p3_y);
            ars.Size = new Size(k_radius, k_radius*1.5);
            ars.SweepDirection = SweepDirection.Counterclockwise;
            pathFigure.Segments.Add(ars);

            ls = new LineSegment();
            ls.Point = new Point(k_p4_x, k_p4_y);
            pathFigure.Segments.Add(ls);
            ars = new ArcSegment();
            ars.Point = new Point(k_out_p1_x, k_out_p1_y);
            ars.Size = new Size(k_radius * 4, k_radius * 2);
            ars.SweepDirection = SweepDirection.Clockwise;
            pathFigure.Segments.Add(ars);
            ars = new ArcSegment();
            ars.Point = new Point(k_p1_x, k_p1_y);
            ars.Size = new Size(k_radius * 4, k_radius * 2);
            ars.SweepDirection = SweepDirection.Clockwise;
            pathFigure.Segments.Add(ars);

            // Add Inputs & output
            // Input 1
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_in1_p1_x, k_in1_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_in1_p2_x, k_in1_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            // Input 2
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_in2_p1_x, k_in2_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_in2_p2_x, k_in2_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);
            // Output 
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_out_p1_x, k_out_p1_y);
            pathFigure.Segments = new PathSegmentCollection();
            ls = new LineSegment();
            ls.Point = new Point(k_out_p2_x, k_out_p2_y);
            pathFigure.Segments.Add(ls);
            pathGeometry.Figures.Add(pathFigure);

            // Setup the path
            path = new Path();
            path.Stretch = Stretch.Fill;
            path.StrokeLineJoin = PenLineJoin.Round;
            path.Stroke = new SolidColorBrush(strokeColor);
            path.Fill = new SolidColorBrush(backgroundColor);
            path.StrokeThickness = k_dbl_stroke_thickness;
            path.MouseLeftButtonDown += new MouseButtonEventHandler(CustomMouseLeftButtonDown);
            path.MouseLeftButtonUp += new MouseButtonEventHandler(CustomMouseLeftButtonUp);
            path.MouseMove += new MouseEventHandler(CustomMouseMove);
            path.MouseEnter += new MouseEventHandler(CustomMouseEnter);
            path.MouseLeave += new MouseEventHandler(CustomMouseLeave);

            pathGroup = new GeometryGroup();
            pathGroup.Children.Add(pathGeometry);

            // Add the distinctive XOR arc
            pathFigure = new PathFigure();
            pathFigure.StartPoint = new Point(k_p2_x - 5, k_p2_y);
            pathFigure.Segments = new PathSegmentCollection();
            ars = new ArcSegment();
            ars.Point = new Point(k_p3_x - 5, k_p3_y);
            ars.Size = new Size(k_radius, k_radius * 1.5);
            ars.SweepDirection = SweepDirection.Counterclockwise;
            pathFigure.Segments.Add(ars);
            pathGeometry = new PathGeometry();
            pathGeometry.Figures = new PathFigureCollection();
            pathFigure.IsClosed = false;
            pathGeometry.Figures.Add(pathFigure);

            pathGroup.Children.Add(pathGeometry);
            path.Data = pathGroup;
            Children.Add(path);

            // Setup output points
            outputLeft.X = k_out_p1_x;
            outputLeft.Y = k_out_p1_y;
            outputRight.X = k_out_p2_x;
            outputRight.Y = k_out_p2_y;

            // Setup rubberband output line
            outputLine = new Line();
            outputLine.Stroke = new SolidColorBrush(rubberbandColor);
            outputLine.StrokeThickness = k_dbl_stroke_thickness;
            outputLine.Visibility = System.Windows.Visibility.Collapsed;

            Children.Add(outputLine);

            // Set the initial position
            path.SetValue(Canvas.LeftProperty, k_initial_left);
            path.SetValue(Canvas.TopProperty, k_initial_top);
            canvLeft = k_initial_left;
            canvTop = k_initial_top;
        }

        protected override int Connect(Device logicDevice, Point clickedPoint)
        {
            double yScale = 1;
            Panel parent = (Panel)Parent;
            if (Application.Current.Host.Content.IsFullScreen)
                yScale = Application.Current.Host.Content.ActualHeight / parent.ActualHeight;

            // If the point clicked was in the upper half of the gate, choose input 1
            if ((clickedPoint.Y/yScale - canvTop) < (k_p1_y) / 2)
            {
                if (inputDevices[k_int_first_input] == null)
                {
                    inputDevices[k_int_first_input] = logicDevice;
                    return k_int_first_input;
                }
            }
            else // Try to connect to the second input
            {
                if (inputDevices[k_int_second_input] == null)
                {
                    inputDevices[k_int_second_input] = logicDevice;
                    return k_int_second_input;
                }
            }
            // Can't connect...
            return k_int_not_connected;
        }

        protected override Point GetInputLocation(int inputNumber)
        {
            Point p = new Point();
            // Figure out the correct point information
            if (inputNumber == k_int_first_input)
            {
                p.X = canvLeft + k_in1_p1_x + k_int_input_x_offset;
                p.Y = canvTop + k_in1_p1_y + k_int_input_y_offset;
            }
            else if (inputNumber == k_int_second_input)
            {
                p.X = canvLeft + k_in2_p1_x + k_int_input_x_offset;
                p.Y = canvTop + k_in2_p1_y + k_int_input_y_offset;
            }
            return p;
        }

        // Sets the state of the specified input to the given value
        public override void SetInputState(int inputNumber, bool state)
        {
            if (inputNumber == k_int_first_input)
                // Set the first input state
                input1State = state;
            else if (inputNumber == k_int_second_input)
                input2State = state;
            // Set the output state - Prevent infinite feedback
            bool newOutputState = (input1State && !input2State) || (!input1State && input2State);
            if (newOutputState != outputState)
            {
                outputState = newOutputState;
                // Set the input state of any connected devices
                UpdateOutputState();
            }
        }
    }
}
